/*******************************************************************************************
 * Copyright (c) 2006-2009 Alex Robson, Rob Simmons, Craig Israel, and Rachel Twyford
 *
 * Use of this software in any form requires that you have both 
 * read and agreed to the following terms:
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions: 
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * Neither the name of Nvigorate nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;
using Nvigorate.Common;
using Nvigorate.Xml;

namespace Nvigorate.Data.Query
{
    [Serializable]
    public class CriteriaClause : IClause, ICloneable, IDisposable, IXmlSerializable
    {
        #region Fields

        private List<IClause> _criteria = new List<IClause>();
        private Conjunctions _conjunction;

        #endregion

        #region Properties
        public List<IClause> Criteria
        {
            get { return _criteria; }
            set { _criteria = value; }
        }
        
        public Conjunctions Conjunction
        {
            get { return _conjunction; }
            set { _conjunction = value; }
        }

        #endregion

        #region Overrides

        public static CriteriaClause operator &(CriteriaClause clauseA, CriteriaClause clauseB)
        {
            CriteriaClause superClause = new CriteriaClause(clauseA);
            return superClause.And(clauseB);
        }

        public static CriteriaClause operator |(CriteriaClause clauseA, CriteriaClause clauseB)
        {
            CriteriaClause superClause = new CriteriaClause(clauseA);
            return superClause.Or(clauseB);
        }

        public override string ToString()
        {
            Queue<IClause> copy = new Queue<IClause>();

            foreach (IClause criterion in _criteria)
                copy.Enqueue(criterion);

            DelimitedBuilder builder = new DelimitedBuilder(" ");
            
            while(copy.Count > 0)
            {
                IClause criterion = copy.Dequeue();

                string conjunction = "";
                if (criterion.Conjunction != Conjunctions.None && copy.Count < _criteria.Count - 1)
                    conjunction = (criterion.Conjunction == Conjunctions.And) ? "AND" : "OR";

                if(criterion.GetType().Equals(typeof(CriteriaClause)))
                {
                    if(conjunction != "")
                        builder.AppendFormat("{0} ({1})", conjunction, ((CriteriaClause)criterion).ToString());
                    else
                        builder.AppendFormat("({0})", ((CriteriaClause)criterion).ToString());
                }
                else
                {
                    if (conjunction != "")
                        builder.AppendFormat("{0} {1}", conjunction, ((Criterion)criterion).ToString());
                    else
                        builder.AppendFormat("{0}", ((Criterion)criterion).ToString());
                    
                }
            }

            return builder.ToString();
        }
        
        #endregion

        #region Methods

        #region Implementation of IClause

        public IClause And()
        {
            _conjunction = Conjunctions.And;
            return this;
        }

        public IClause Or()
        {
            _conjunction = Conjunctions.Or;
            return this;
        }

        #endregion

        public CriteriaClause And(IClause criterion)
        {
            criterion.Conjunction = Conjunctions.And;
            _criteria.Add(criterion);
            return this;
        }

        public CriteriaClause Or(IClause criterion)
        {
            criterion.Conjunction = Conjunctions.Or;
            _criteria.Add(criterion);
            return this;
        }

        #endregion

        #region IProvideXlement Members

        public object ParseXElement(XElement element)
        {
            _conjunction = (Conjunctions)Enum.Parse(typeof(Conjunctions), element.GetAttributeValue("conjunction") ?? "None");
            _criteria.AddRange(
                element.Elements()
                    .Select(e =>
                                e.Name != "clause" ? 
                                    QueryDeserializer.DeserializeAmbiguousChild<IClause>(e) : 
                                    (IClause) new CriteriaClause().ParseXElement(e))
                    );

            return this;
        }

        public XElement GetXElementRepresentation()
        {
            XElement element = new XElement
                ("clause",
                 _criteria.Select(c => ((ILinqToXml)c).GetXElementRepresentation())
                );

            if(_conjunction != Conjunctions.None)
                element.Add(new XAttribute("conjunction", _conjunction.ToString()));

            return element;
        }

        #endregion

        #region Implementation of IXmlSerializable

        /// <summary>
        ///                     This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute" /> to the class.
        /// </summary>
        /// <returns>
        ///                     An <see cref="T:System.Xml.Schema.XmlSchema" /> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)" /> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)" /> method.
        /// </returns>
        public XmlSchema GetSchema()
        {
            throw new System.NotImplementedException();
        }

        /// <summary>
        ///                     Generates an object from its XML representation.
        /// </summary>
        /// <param name="reader">
        ///                     The <see cref="T:System.Xml.XmlReader" /> stream from which the object is deserialized. 
        ///                 </param>
        public void ReadXml(XmlReader reader)
        {
            //do nothing
        }

        /// <summary>
        ///                     Converts an object into its XML representation.
        /// </summary>
        /// <param name="writer">
        ///                     The <see cref="T:System.Xml.XmlWriter" /> stream to which the object is serialized. 
        ///                 </param>
        public void WriteXml(XmlWriter writer)
        {
            //do nothing
        }

        #endregion

        #region Implementation of ICloneable

        public object Clone()
        {
            CriteriaClause clone = new CriteriaClause();
            clone.Conjunction = _conjunction;
            _criteria.ForEach(c => clone.Criteria.Add((IClause)((ICloneable)c).Clone()));
            return clone;
        }

        #endregion

        #region Constructors

        public CriteriaClause()
        {
        }

        public CriteriaClause(XElement element)
        {
            ParseXElement(element);
        }

        public CriteriaClause(IClause initialCriterion)
        {
            _criteria.Add(initialCriterion);
        }

        public CriteriaClause(IEnumerable<IClause> criterion)
        {
            _criteria.AddRange(criterion);
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            _criteria.Clear();
            _criteria = null;
        }

        #endregion
    }
}